// Copyright (C) 2011 by Christopher Diggins
// Usage is permitted under the terms of the MIT License 1.0. 	

/** \file
	Contains implementation of serialization and deserialization functions.
	\see \ref serialize
*/

void serialize_vm(VM* vm, Val* dest, int include_stacks)
{
}

void deserialize_vm(VM* vm, Val** src)
{
}

#ifdef IGNORE_THIS

/** Serializes the entire active VM state. If the stacks are not 
	included this effectively creates a copy of a byte-code file. */
void serialize_vm(VM* vm, Val* dest, int include_stacks) {
	/*
	Object table, constants, adata, odata, stack, aux;
	uint ip;

	obj_set_type(dest, StoreType);

	// Initialize objects
	table_init(&table, 0);
	obj_init(&adata);
	obj_init(&odata);
	obj_init(&stack);
	obj_init(&constants);
	obj_init(&aux);

	// First thing we do is create a local copy of the 
	// constants, stack, and auxiliary data.

	// We get stack data only if the flag is set
	if (include_stacks) {
		vm_get_stack(vm, &stack);
		vm_get_aux_stack(vm, &aux);
	}

	// Get a copy of the constants
	array_copy_and_alloc(&vm->constants, &constants);

	// Get the object data for constants, main stack, and auxiliary stack.
	serialize_get_object_data_children(&constants, &odata, &table);
	serialize_get_object_data_children(&stack, &odata, &table);
	serialize_get_object_data_children(&aux, &odata, &table);
	
	// Gather the array data for all objects
	serialize_get_all_array_entries(&odata, &table, &adata);

	// The constants, stack, and aux are all lists of indexes into the 
	// object data array (except when they are ints).
	serialize_convert_object_pointers(&constants, &table);
	serialize_convert_object_pointers(&stack, &table);
	serialize_convert_object_pointers(&aux, &table);

	// 1) Code block
	serialize_blob_with_size(dest, vm->code, vm->ccnt);

	// 2) Instruction pointer 
	ip = vm->ip - vm->code;
	array_push(dest, (Val*)ip);

	// 3) Constants 
	serialize_object_array_as_blob(dest, &constants);

	// 4) Main stack 
	serialize_object_array_as_blob(dest, &stack);

	// 5) Auxiliary stack
	serialize_object_array_as_blob(dest, &aux);

	// 6) Object data
	serialize_object_array_as_blob(dest, &odata);

	// 7) Array data
	serialize_object_array_as_blob(dest, &adata);

	// clean-up
	table_free(&table);	
	array_free(&adata);
	*/
}

/** Pushes binary data on the array, recast as a number of Val**. */
void serialize_blob(Val* o, void* x, uint size) {
	uint i;	
	Val** tmp = (Val**)x;
	// TODO: fix this so that padding works.
	// Currently the size has to be a multiple of four.
	assert(size % sizeof(Val*) == 0);
	size /= sizeof(Val*);
	for (i = 0; i < size; ++i)
		array_push(o, *tmp++);
}

/** Pushes binary data on the array, recast as a number of Val**. 
	Only accepts data which is aligned by 4. Pushes the size of 
	the data first */
void serialize_blob_with_size(Val* o, void* x, uint size) {	
	assert(size % sizeof(Val*) == 0);
	array_push(o, (Val*)size);
	serialize_blob(o, x, size);
}

/** Returns the object stored in position n of a blob. */
Val*	serialize_get_object_from_blob(Val* src, uint n) {
	return (Val*)array_begin(src) + n;
}

/** Returns the number of objects that are stored in a blob. */
uint serialize_num_objects_in_blob(Val* src) {	
	assert(sizeof(Object) > sizeof(Val*));
	assert(sizeof(Object) % sizeof(Val*) == 0);
	return array_count(src) / (sizeof(Object) / sizeof(Val*));
}

/** Converts an array of Val* to their indexes. */ 
void serialize_convert_object_pointers(Val* dest, Val* table) {
	Val **begin, **end;
	array_foreach(begin, end, dest) {
		if (val_is_obj(*begin)) {
			KeyVal* kv = table_find(table, (uint)*begin);
			uint n = val_to_int(kv->value);
			n = (n + 1) << 1;
			assert(table_slot_occupied(kv));		
			*begin = (Val*)n;
		}
	}	
}

/** Pushes an object's binary data on the array.  */
void serialize_object_array_as_blob(Val* o, Val* src) {
	serialize_blob_with_size(o, array_begin(src), array_count(src) * 4);
}

/** Serialize a copy of an object and all objects it references to dest if not
	already contained in the table. The serialized object has its 
	reference count pinned because after serialization the reference count
	is no longer reliable. This is because all garbage (which might
	be responsible for reference count) is removed. */
void serialize_get_object_data(Val* src, Val* dest, Val* table) {
	Object tmp = *src;
	uint n;

	// Find the value in the table
	if (table_contains(table, (uint)src))
		return;

	// Get the index of the new object. 
	n = (Val*)array_end(dest) - (Val*)array_begin(dest);
	assert(n * 2 == array_count(dest));

	// Add this as a new entry in the table.
	table_add(table, (uint)src, val_from_int(n));

	// Add a copy of the object.
	// We have to maximize the references, because once serialized
	// the number of references is no longer reliable. When serializing
	// we don't serialize dangling garbage. 
	tmp.refs = MAX_REFS;
	serialize_blob(dest, &tmp, sizeof(Object));
	assert((n + 1) * 2 == array_count(dest));
	serialize_get_object_data_children(src, dest, table);
}

/** Recursively get object data for all child objects. */
void serialize_get_object_data_children(Val* src, Val* dest, Val* table) {
	Val **begin, **end;
	if (array_count(src) > 0) 
		array_foreach(begin, end, src)
			serialize_get_object_data((Val*)*begin, dest, table);
}

/** Serializes a single array entry. */
void serialize_get_array_entry(Val* val, Val* table, Val* adata) {
	// Look-up other values in the table. 
	// These are indexes into "odata". 
	// We shift left because the first bit is used to identify 
	// Val* encoded integers.
	// We add "one" to avoid confusion between NULL pointers
	// and the first index.
	KeyVal* kv = table_find(table, (uint)val);
	uint n = (uint)kv->value;
	assert(table_slot_occupied(kv));		
	n = (n + 1) << 1;
	array_push(adata, (Val*)n);
}

/** Adds array entries to adata for a single object. */
void serialize_get_array_entries(Val* o, Val* table, Val* adata) {
	Val **begin, **end;
	KeyVal *kvbegin, *kvend;
	int i = 0;
	if (array_count(o) == 0)
		return;
	if (obj_is_tbl(o)) {
		kvbegin = table_begin(o);
		kvend = table_end(o);
		for (; kvbegin != kvend; ++kvbegin) {
			if (table_slot_occupied(kvbegin)) {
				array_push(adata, (val_from_int)((int)kvbegin->key));
				serialize_get_array_entry(kvbegin->value, table, adata); 
			}
		}
	}
	else {
		array_foreach(begin, end, o) {
			serialize_get_array_entry(*begin, table, adata); 
		}
	}
}

/** Adds array entries for all objects in odata. */
void serialize_get_all_array_entries(Val* odata, Val* table, Val* adata) {
	Val* begin = (Val*)array_begin(odata);
	Val* end = (Val*)array_end(odata);
	for (; begin != end; begin++)
		serialize_get_array_entries(begin, table, adata);
}

/** Deserialize an object encoded on a stream. */
Val** deserialize_object_as_blob(Val** src, Val* dest) {	
	Val* tmp = (Val*)src;
	*dest = *tmp++;
	return (Val**)tmp;
}

/** Deserialize object array data encoded on a stream. */
Val** deserialize_object_array_as_blob(Val** src, Val* dest) {
	uint i;
	uint n = (uint)*src++;
	assert(n % 4 == 0);
	n /= 4;
	for (i=0; i < n; ++i) 
		array_push(dest, *src++);
	return src;
}

/** Restores the object pointers in an array for a particular object. */
void deserialize_restore_pointers(Val* dest, Val* os) {	
	uint i = 0;
	uint key;
	Val* v;
	Object tbl;
	Val **begin, **end;
	if (array_empty(dest))
		return;
	array_foreach(begin, end, dest) {
		// These are indexes into the "os" array. 
		i = (uint)*begin;
		i = (i >> 1) - 1;
		v = array_at(os, i);	
		*begin = v;
	}

	// Tables are serialized as associative lists. This is now 
	// the time that we should recreate the table.
	if (obj_is_tbl(dest)) {
		assert(array_count(dest) % 2 == 0);
		table_init(&tbl, 0);
		begin = array_begin(dest);
		end = array_end(dest);
		while (begin < end) {
			key = (uint)*begin++;
			v = *begin++;
			table_add(&tbl, key, v);
		}
		// Free the old memory, and replace it with the new table's data.
		obj_free(dest);
		dest->data = tbl.data;
	}
}

/** Restores the array data for an object. */
void deserialize_restore_arrays(Val* dest, Val* os, Val* adata) {	
	Val **vadata;
	uint i, n, m;
	n = (uint)dest->data;	// This is an index into adata
	if (!n) return;				// Check if there is data.
	--n;						// This index was offset by one, so correct
	assert(n >= 0);
	assert(n < array_count(adata));
	vadata = array_begin(adata);
	vadata += n;
	m = (uint)*vadata++;	// The first entry at adata is the size of array.
	assert(m > 0);			// It should be a non-zero size
	assert(m + n <= array_count(adata)); // Bounds checking
	
	// Allocate the array and push the array data into it.
	array_alloc(dest, m); 		
	for (i=0; i < m; ++i) 
		array_push(dest, *vadata++);

	deserialize_restore_pointers(dest, os);
}

/** Loads a VM's state from an object. */
void deserialize_vm(VM* vm, Val** src) {
	Object *obegin, *oend;
	Object *tmp;
	Val **vbegin, **vend;
	Object stack, aux, adata, odata, os;
	uint n;

	// Initialize the objects
	obj_init(&odata);
	obj_init(&adata);
	obj_init(&stack);
	obj_init(&aux);
	obj_init(&os);

	// Clear the VM's memory
	vm_clear(vm);
	
	// 1) Code block
	n = (uint)*src++;
	vm->ccnt = n;
	assert(n % 4 == 0);
	vm_set_code(vm, (byte*)src, vm->ccnt);
	src += n / 4;

	// 2) Instruction pointer 
	n = (uint)*src++;
	assert(!n || n < vm->ccnt);
	vm->ip += n;

	// 3) Constants
	src = deserialize_object_array_as_blob(src, &vm->constants);
	
	// 4) Main stack 
	src = deserialize_object_array_as_blob(src, &stack);

	// 5) Auxiliary stack
	src = deserialize_object_array_as_blob(src, &aux);

	// 6) Object data
	src = deserialize_object_array_as_blob(src, &odata);

	// 7) Array data
	src = deserialize_object_array_as_blob(src, &adata);

	// Allocate the objects from odata
	obegin = (Val*)array_begin(&odata);
	oend = (Val*)array_end(&odata);
	for (; obegin != oend; ++obegin) {
		tmp = mem_obj_alloc(&vm->mem);
		*tmp = *obegin;
		array_push(&os, (Val*)tmp);
	}

	// Restore the array data for each object.
	array_foreach(vbegin, vend, &os) {
		tmp = (Val*)*vbegin;
		if ((uint)tmp->data)
			deserialize_restore_arrays(tmp, &os, &adata);
	}

	// Restore the pointers for constants and the two stacks.
	deserialize_restore_pointers(&vm->constants, &os);
	deserialize_restore_pointers(&stack, &os);
	deserialize_restore_pointers(&aux, &os);

	// Finally copy the stack data to the final location
	array_foreach(vbegin, vend, &stack) vm_push(vm, *vbegin);
	array_foreach(vbegin, vend, &aux) vm_push_aux(vm, *vbegin);

	// Clean-up all objects
	obj_free(&os);
	obj_free(&adata);
	obj_free(&odata);
	obj_free(&stack);
	obj_free(&aux);
}

#endif
